package com.nulldev.util.web.HttpServer3.rewrite;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.channels.WritableByteChannel;
import java.util.Map;

import com.nulldev.util.concurrency.threadIt.v4.emapi.cntft.ControllableFutureTask;
import com.nulldev.util.data.Variables;
import com.nulldev.util.data.Sets.DataSet;
import com.nulldev.util.internal.backport.concurrency9.Maps;
import com.nulldev.util.io.IOUtils;
import com.nulldev.util.io.NIOUtils;
import com.nulldev.util.web.HttpServer3.Page;
import com.nulldev.util.web.HttpServer3.Response;
import com.nulldev.util.web.HttpServer3.legacyPages.v1.HttpPage;
import com.nulldev.util.web.HttpServer3.legacyPages.v1.RequestType;
import com.nulldev.util.web.HttpServer3.legacyPages.v2.HttpPage2;
import com.nulldev.util.web.HttpServer3.pages.v3.ASyncHttpPage3;
import com.nulldev.util.web.HttpServer3.pages.v3.HttpPage3;
import com.nulldev.util.web.HttpServer3.pages.v3.HttpPage3.Request;
import com.nulldev.util.web.HttpServer3.pages.v3.RequestProtocolType;
import com.nulldev.util.web.HttpServer3.util.BlankHashMap;

final class HttpServerCache {

	private static final BlankHashMap<String, String> STUB_HEADERS = new BlankHashMap<String, String>();

	private static final String DEFAULT_IP = "127.0.0.1";
	private static final String DEFAULT_URL = "/cache";

	static class CachedResponse {

		byte[] data;
		final Map<String, String> headers;

		public CachedResponse(final Response r) throws IOException {
			__read_data(r);

			this.headers = Maps.copyOf(r.getCustomHeaders());
		}

		void __read_data(final Response r) throws IOException {
			if (r.copyable()) {
				final ByteArrayOutputStream baos = new ByteArrayOutputStream(IOUtils.MEMORY_ALLOC_BUFFER);
				r.getCopyable().writeTo(baos);
				this.data = baos.toByteArray();
				IOUtils.closeQuietly(baos);
			} else if (r.nioCopyable()) {
				final ByteArrayOutputStream baos = new ByteArrayOutputStream(IOUtils.MEMORY_ALLOC_BUFFER);
				final WritableByteChannel wbc = NIOUtils.makeWritableByteChannel(baos);
				r.getNIOCopyable().writeTo(wbc);

				this.data = baos.toByteArray();
				IOUtils.closeQuietly(baos);
			} else {
				this.data = IOUtils.toByteArray(r.getInputStream());
			}
		}

	}

	static class HttpServerCacheEntry {
		final Page page;
		final String id;

		CachedResponse response;

		public HttpServerCacheEntry(final Page page, final String id) {
			this.page = Variables.requireNonNullObject(page, "page");
			this.id = Variables.requireNonNullObject(id, "id");
		}

		CachedResponse __populate_or_get_data() throws IOException {
			if (this.response == null) {
				if (page instanceof ASyncHttpPage3) {
					final ControllableFutureTask<Response> _r = new ControllableFutureTask<Response>();
					((ASyncHttpPage3) this.page).onFutureRequest(new Request(DEFAULT_URL, DEFAULT_IP, RequestProtocolType.HTTP_1_1), _r);
					final Response r = _r.waitTillCompletion();

					return (this.response = new CachedResponse(r));
				} else if (page instanceof HttpPage3) {
					final Response resp = ((HttpPage3) this.page).onRequest(new Request(DEFAULT_URL, DEFAULT_IP, RequestProtocolType.HTTP_1_1));

					return (this.response = new CachedResponse(resp));
				} else if (page instanceof HttpPage2) {
					final DataSet<Integer, InputStream> resp = ((HttpPage2) this.page).request(RequestType.GET, DEFAULT_URL, STUB_HEADERS, null, DEFAULT_IP);
					final Response compat = new Response(resp);

					return (this.response = new CachedResponse(compat));
				} else if (page instanceof HttpPage) {
					final HttpPage p = ((HttpPage) this.page);
					final byte[] data;

					if (p.sendRaw()) {
						data = IOUtils.toByteArray(new FileInputStream(p.getPath()));
					} else if (p.isOverBytes()) {
						data = p.handleRequest_Alt("/cache?caching=true", "0.0.0.0", RequestType.GET);
					} else {
						data = p.handleRequest("/cache?caching=true", "0.0.0.0", RequestType.GET).getBytes();
					}

					return (this.response = new CachedResponse(new Response(200, new ByteArrayInputStream(data))));
				} else {
					throw new UnsupportedOperationException("Unsupported page type: " + page);
				}
			} else {
				return this.response;
			}
		}
	}
}
